<html>
    <head>
        <title>应用服务  Application Services</title>
    </head>
    <body>
        <script>
            // 应用服务

                    /*
                            应用程序服务用于 "实现应用程序的用例"。它们用于将"领域逻辑" 公开给表示层。

                            以DTO（数据传输对象）作为参数从表示层调用应用程序服务。

                            它使用"领域域对象"来执行一些特定的"业务逻辑"，并（可选）将DTO返回给表示层。

                            因此，应用服务将"表示层"与"领域层"完全隔离。
                    */

                    /*
                        一、应用服务示例

                                1、假设有一个Book实体（实际上是一个聚合根）：

                                                public class Book : AggregateRoot<Guid>
                                                {
                                                    public const int MaxNameLength = 128;

                                                    public virtual string Name { get; protected set; }

                                                    public virtual BookType Type { get; set; }

                                                    public virtual float? Price { get; set; }

                                                    protected Book()
                                                    {
                                                        
                                                    }

                                                    public Book(Guid id, [NotNull] string name, BookType type, float? price = 0)
                                                    {
                                                        Id = id;
                                                        Name = CheckName(name);
                                                        Type = type;
                                                        Price = price;
                                                    }

                                                    // 与实体相关的业务逻辑
                                                    public virtual void ChangeName([NotNull] string name)
                                                    {
                                                        Name = CheckName(name);
                                                    }

                                                    // 与实体相关的业务逻辑
                                                    private static string CheckName(string name)
                                                    {
                                                        if (string.IsNullOrWhiteSpace(name))
                                                        {
                                                            throw new ArgumentException(
                                                                $"name can not be empty or white space!");
                                                        }

                                                        if (name.Length > MaxNameLength)
                                                        {
                                                            throw new ArgumentException(
                                                                $"name can not be longer than {MaxNameLength} chars!");
                                                        }

                                                        return name;
                                                    }
                                                }

                                                Book实体有一个MaxNameLength，它定义了Name属性的最大长度。

                                                Book构造函数和ChangeName方法以确保Name始终是一个有效值。请注意，Name的setter不是public。


                                                （ABP不会强迫你这样设计你的实体。它只是可以为所有属性提供公共get/set。是否完全实施DDD实践是你的决定。）



                                2、定义服务接口：  IBookService

                                                ABP中，应用程序服务应该实现IApplicationService接口。最好为每个应用程序服务创建一个接口：

                                                        public interface IBookAppService : IApplicationService
                                                        {
                                                            Task CreateAsync(CreateBookDto input);
                                                        }

                                                将实现一个Create方法作为示例。CreateBookDto是这样定义的：

                                                        public class CreateBookDto
                                                        {
                                                            [Required]
                                                            [StringLength(Book.MaxNameLength)]
                                                            public string Name { get; set; }

                                                            public BookType Type { get; set; }

                                                            public float? Price { get; set; }
                                                        }


                                3、定义服务实现：  BookAppService

                                                        public class BookAppService : ApplicationService, IBookAppService
                                                        {
                                                            private readonly IRepository<Book, Guid> _bookRepository;

                                                            public BookAppService(IRepository<Book, Guid> bookRepository)
                                                            {
                                                                _bookRepository = bookRepository;
                                                            }

                                                            public async Task CreateAsync(CreateBookDto input)
                                                            {
                                                                var book = new Book(
                                                                    GuidGenerator.Create(),
                                                                    input.Name,
                                                                    input.Type,
                                                                    input.Price
                                                                );

                                                                await _bookRepository.InsertAsync(book);
                                                            }
                                                        }

                                                    BookAppService继承自ApplicationService基类。
                                                    
                                                    它不是必需的，但是ApplicationService类为常见的应用程序服务需求提供了有用的属性，例如此服务中使用的GuidGenerator。

                                                    如果我们没有继承它，我们需要手动注入IGuidGenerator服务（请参阅guid生成文档）。

                                                    BookAppService按预期实现了IBookAppService。

                                                    BookAppService注入IRepository<Book, Guid>（请参阅存储库）并在CreateAsync方法中使用它将新实体插入数据库。

                                                    CreateAsync使用Book实体的构造函数根据给定input的属性创建一本新书。

                    */

                    /*
                        二、数据传输对象   （DTO）

                                        应用程序服务获取和返回DTO而不是实体。

                                        ABP不强制执行此规则。

                                        但是，将实体暴露给表示层（或远程客户端）存在重大问题，因此不建议这样做。

                                        有关详细信息，请参阅DTO文档。
                    */


                    /*
                        三、对象映射

                                        上面的CreateAsync方法从给定的CreateBookDto对象手动创建一个Book实体，因为Book实体强制执行它（我们是这样设计的）。

                                        然而，在许多情况下，使用自动对象映射从类似对象设置对象的属性非常实用。

                                        ABP提供了一个对象到对象映射基础架构来使这变得更加容易。

                                        对象到对象的映射提供抽象，默认情况下由AutoMapper库实现。



                                        让我们创建另一个方法来获取一本书。首先，在IBookAppService接口中定义方法：

                                                    public interface IBookAppService : IApplicationService
                                                    {
                                                        Task CreateAsync(CreateBookDto input);

                                                        Task<BookDto> GetAsync(Guid id); //New method
                                                    }

                                        BookDto是一个简单的DTO类，定义如下：

                                                    public class BookDto
                                                    {
                                                        public Guid Id { get; set; }

                                                        public string Name { get; set; }

                                                        public BookType Type { get; set; }

                                                        public float? Price { get; set; }
                                                    }

                                        AutoMapper需要创建一个映射文档类。例子：

                                                    public class MyProfile : Profile
                                                    {
                                                        public MyProfile()
                                                        {
                                                            CreateMap<Book, BookDto>();
                                                        }
                                                    }

                                        然后，你应该使用AbpAutoMapperOptions注册配置文件：

                                                    [DependsOn(typeof(AbpAutoMapperModule))]
                                                    public class MyModule : AbpModule
                                                    {
                                                        public override void ConfigureServices(ServiceConfigurationContext context)
                                                        {
                                                            Configure<AbpAutoMapperOptions>(options =>
                                                            {
                                                                // 将MyModule 所在程序集的所有映射配置添加到AutoMapper中。
                                                                options.AddMaps<MyModule>();
                                                            });
                                                        }
                                                    }

                                        AddMaps注册在给定类的程序集中定义的所有配置文件类，通常是你的模块类。它还注册属性映射。

                                        然后可以实现GetAsync方法，如下所示：

                                                    public async Task<BookDto> GetAsync(Guid id)
                                                    {
                                                        var book = await _bookRepository.GetAsync(id);
                                                        return ObjectMapper.Map<Book, BookDto>(book);
                                                    }

                                        有关更多信息，请参阅"对象映射文档":https://abp.io/docs/latest/framework/infrastructure/object-to-object-mapping

                    */

                    /*
                        四、验证 Validation

                                   应用程序服务方法的输入会被自动验证（如ASP.NET核心控制器操作）。
                                   
                                   你可以使用标准数据注释属性或自定义验证方法来执行验证。

                                   ABP还确保输入不为空。
                    */

                    /*
                        五、授权  Authorization

                                    可以对应用程序服务方法使用"声明式授权"和 "命令式授权"。


                                    有关更多信息，请参阅授权文档: https://abp.io/docs/latest/framework/fundamentals/authorization
                    */

                    /*
                        六、CRUD应用服务

                                    如果你需要创建一个简单的CRUD应用程序服务，它具有Create、Update、Delete和Get方法，你可以使用ABP的基类轻松构建你的服务。

                                    你可以从CrudAppService继承。


                                    1、CRUD 应用服务示例：
                                    
                                                创建从ICrudAppService接口继承的IBookAppService接口。

                                                    public interface IBookAppService : 
                                                        ICrudAppService< //  此接口声明了基本的CRUD操作
                                                            BookDto, // 输出Dto
                                                            Guid, // 主键类型
                                                            PagedAndSortedResultRequestDto, // 排序分页参数Dto
                                                            CreateUpdateBookDto, // 输入Dto
                                                            CreateUpdateBookDto> // 输入Dto
                                                    {
                                                    }

                                                ICrudAppService具有 "通用参数" 来获取实体的主键类型和CRUD操作的DTO类型
                                                    
                                                （它不获取实体类型，因为实体类型没有暴露给使用此接口的客户端）。

                                                ★、为应用程序服务创建接口是很好的做法，但ABP不需要。你可以跳过接口部分。、

                                                ICrudAppService声明以下方法：

                                                            public interface ICrudAppService<
                                                                TEntityDto,
                                                                in TKey,
                                                                in TGetListInput,
                                                                in TCreateInput,
                                                                in TUpdateInput>
                                                                : IApplicationService
                                                                where TEntityDto : IEntityDto<TKey>
                                                            {
                                                                Task<TEntityDto> GetAsync(TKey id);

                                                                Task<PagedResultDto<TEntityDto>> GetListAsync(TGetListInput input);

                                                                Task<TEntityDto> CreateAsync(TCreateInput input);

                                                                Task<TEntityDto> UpdateAsync(TKey id, TUpdateInput input);

                                                                Task DeleteAsync(TKey id);
                                                            } 
                                                        
                                                此示例中使用的DTO类是BookDto和CreateUpdateBookDto：
                                           
                                                            // 输出Dto
                                                            public class BookDto : AuditedEntityDto<Guid>
                                                            {
                                                                public string Name { get; set; }

                                                                public BookType Type { get; set; }

                                                                public float Price { get; set; }
                                                            }

                                                            // 输入Dto
                                                            public class CreateUpdateBookDto
                                                            {
                                                                [Required]
                                                                [StringLength(128)]
                                                                public string Name { get; set; }

                                                                [Required]
                                                                public BookType Type { get; set; } = BookType.Undefined;

                                                                [Required]
                                                                public float Price { get; set; }
                                                            }

                                                配置文档类：

                                                            public class MyProfile : Profile
                                                            {
                                                                public MyProfile()
                                                                {
                                                                    CreateMap<Book, BookDto>();
                                                                    CreateMap<CreateUpdateBookDto, Book>();
                                                                }
                                                            }


                                                        CreateUpdateBookDto由创建和更新操作共享，但你也可以使用单独的DTO类。

                                                最后，BookAppService的实现非常简单：

                                                            public class BookAppService : 
                                                                CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
                                                                                    CreateUpdateBookDto, CreateUpdateBookDto>,
                                                                IBookAppService
                                                            {
                                                                public BookAppService(IRepository<Book, Guid> repository) 
                                                                    : base(repository)
                                                                {
                                                                }
                                                            }

                                                CrudAppService实现ICrudAppService接口中声明的所有方法。然后，你可以添加自己的自定义方法或覆盖和自定义基方法。

                                                CrudAppService有不同的版本，得到不同数量的通用参数。使用适合你的。



                                2、AbstractKeyCrudAppService

                                                CrudAppService需要有一个Id属性作为实体的主键。如果你使用的是复合键，则不能使用它。

                                                AbstractKeyCrudAppService实现了相同的ICrudAppService接口，但这次没有假设你的实体为单字段主键。

                                             示例：

                                                    假设你有一个District实体，CityId和Name作为复合主键。

                                                    使用AbstractKeyCrudAppService需要自己实现 "DeleteByIdAsync" 和 "GetEntityByIdAsync" 方法：
                                                                
                                                                public class DistrictAppService
                                                                    : AbstractKeyCrudAppService<District, DistrictDto, DistrictKey>
                                                                {
                                                                    public DistrictAppService(IRepository<District> repository) 
                                                                        : base(repository)
                                                                    {
                                                                    }

                                                                    protected async override Task DeleteByIdAsync(DistrictKey id)
                                                                    {
                                                                        await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name);
                                                                    }

                                                                    protected async override Task<District> GetEntityByIdAsync(DistrictKey id)
                                                                    {
                                                                        var queryable = await Repository.GetQueryableAsync();
                                                                        return await AsyncQueryableExecuter.FirstOrDefaultAsync(
                                                                            queryable.Where(d => d.CityId == id.CityId && d.Name == id.Name)
                                                                        );
                                                                    }
                                                                }

                                                    此实现要求你创建一个表示复合键的类：

                                                                public class DistrictKey
                                                                {
                                                                    public Guid CityId { get; set; }

                                                                    public string Name { get; set; }
                                                                }


                                 3、CRUD应用服务的授权  Authorize

                                            授权基础应用服务方法有两种方式：

                                                    (1)、你可以在服务的构造函数中设置策略属性（xxxPolcyName）。

                                                            eg： 
                                                                public class MyPeopleAppService : CrudAppService<Person, PersonDto, Guid>
                                                                {
                                                                    public MyPeopleAppService(IRepository<Person, Guid> repository) 
                                                                        : base(repository)
                                                                    {
                                                                        GetPolicyName = "...";  // 设置策略名称
                                                                        GetListPolicyName = "..."; // 设置策略名称
                                                                        CreatePolicyName = "..."; // 设置策略名称
                                                                        UpdatePolicyName = "..."; // 设置策略名称
                                                                        DeletePolicyName = "..."; // 设置策略名称
                                                                    }
                                                                }

                                                    (2)、你可以重写服务中的检查方法（CheckXxxPolcyAsync）。例子：

                                                            eg:
                                                                public class MyPeopleAppService : CrudAppService<Person, PersonDto, Guid>
                                                                {
                                                                    public MyPeopleAppService(IRepository<Person, Guid> repository) 
                                                                        : base(repository)
                                                                    {
                                                                    }

                                                                    // 重写CRUD服务基类中的检查方法
                                                                    protected async override Task CheckDeletePolicyAsync()
                                                                    {
                                                                        await AuthorizationService.CheckAsync("...");
                                                                    }
                                                                }


                                            你可以在CheckDeletePolicyAsync方法中执行任何逻辑。
                                        
                                            在任何未经授权的情况下，都应该抛出AbpAuthorizationException，如AuthorizationService.CheckAsync已经这样做了。



                                4、基本属性和方法 (CRUD 服务中的)

                                           CRUD应用程序服务基类提供了许多有用的基本方法，你可以覆盖以根据你的需求对其进行自定义。
                                           
                                           (1)、CRUD方法

                                                       这些是基本的CRUD方法。你可以覆盖其中任何一个以完全自定义操作。这里，方法的定义：
                                                       
                                                                Task<TGetOutputDto> GetAsync(TKey id);
                                                                Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input);
                                                                Task<TGetOutputDto> CreateAsync(TCreateInput input);
                                                                Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input);
                                                                Task DeleteAsync(TKey id);、


                                            (2)、查询方法

                                                      这些方法是可以控制如何从数据库中查询实体的底层方法。  

                                                            ①、CreateFilteredQuery以创建由给定输入过滤的IQueryable<TEntity>。

                                                                        如果你的TGetListInput类包含任何过滤器，则可以覆盖此方法并过滤查询。
                                                                        
                                                                        默认情况下，它返回（未过滤的）存储库（已经是IQueryable<TEntity>）。

                                                            ②、ApplyPaging用于对查询进行分页。
                                                                        
                                                                        如果你的TGetListInput已经实现了IPagedResultRequest，你不需要覆盖它，因为ABP会自动理解它并执行分页。

                                                            ③、ApplySorting用于对查询进行排序（按…排序）。

                                                                        如果你的TGetListInput已经实现了ISortedResultRequest，ABP会自动对查询进行排序。

                                                                        如果不是，如果你的实体实现了标准的IHasCreationTime接口，它会回退到尝试按创建时间排序的ApplyDefaultSorting。

                                                            ④、GetEntityByIdAsync用于通过id获取实体，默认调用Repository.GetAsync(id)。
   
                                                            ⑤、DeleteByIdAsync用于按id删除实体，默认调用Repository.DeleteAsync(id)。
                                                            

                                5、对象映射

                                         这些方法用于将实体转换为DTO，反之亦然。默认情况下，它们使用IObjectMapper。


                                                        ①、MapToGetOutputDtoAsync用于将实体映射到从GetAsync、CreateAsync和UpdateAsync方法返回的DTO

                                                                            或者，如果不需要执行任何异步操作，你可以覆盖MapToGetOutputDto。

                                                        ②、MapToGetListOutputDtosAsync用于将实体列表映射到从GetListAsync方法返回的DTO列表。

                                                                            它使用MapToGetListOutputDtoAsync映射列表中的每个实体。

                                                                            你可以根据自己的情况覆盖其中一个。

                                                                            或者，如果不需要执行任何异步操作，你可以覆盖MapToGetListOutputDto。

                                                        ③、MapToEntityAsync方法有两个重载：

                                                                            MapToEntityAsync(TCreateInput)用于从TCreateInput创建实体。

                                                                            MapToEntityAsync(TUpdateInput, TEntity)用于从TUpdateInput更新现有实体。
                                                                
                    */

                    /*
                        七、杂项

                                   1、 使用流  Working with Streams

                                             Stream对象本身不可序列化。
                                             
                                             因此，如果你直接使用Stream作为应用程序服务的参数或返回值，你可能会遇到问题。

                                             ABP提供了一种特殊类型，IRemoteStreamContent用于获取或返回应用程序服务中的流。


                                             eg：可用于获取和返回流的应用程序服务接口

                                                    using System;
                                                    using System.Threading.Tasks;
                                                    using Volo.Abp.Application.Services;
                                                    using Volo.Abp.Content;

                                                    namespace MyProject.Test
                                                    {
                                                        public interface ITestAppService : IApplicationService
                                                        {
                                                            Task Upload(Guid id, IRemoteStreamContent streamContent);
                                                            Task<IRemoteStreamContent> Download(Guid id);  // 以返回IRemoteStreamContent下载流

                                                            Task CreateFile(CreateFileInput input);
                                                            Task CreateMultipleFile(CreateMultipleFileInput input);
                                                        }

                                                        // 创建文件输入DTO
                                                        public class CreateFileInput
                                                        {
                                                            public Guid Id { get; set; }

                                                            public IRemoteStreamContent Content { get; set; } // 以IRemoteStreamContent作为文件内容
                                                        }

                                                        // 创建多个文件输入DTO
                                                        public class CreateMultipleFileInput
                                                        {
                                                            public Guid Id { get; set; }

                                                            public IEnumerable<IRemoteStreamContent> Contents { get; set; }
                                                        }
                                                    }


                                            需要配置 "AbpAspNetCoreMvcOptions"将DTO类添加到FormBodyBindingIgnoredTypes
                                            
                                            才能在DTO（Data Transfer Object）中使用IRemoteStreamContent

                                                    Configure<AbpAspNetCoreMvcOptions>(options =>
                                                    {
                                                        options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(CreateFileInput));
                                                        options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(CreateMultipleFileInput));
                                                    });

                                            eg: 可用于获取和返回流的应用程序服务实现

                                                    using System;
                                                    using System.IO;
                                                    using System.Threading.Tasks;
                                                    using Volo.Abp;
                                                    using Volo.Abp.Application.Services;
                                                    using Volo.Abp.Content;

                                                    namespace MyProject.Test
                                                    {
                                                        public class TestAppService : ApplicationService, ITestAppService
                                                        {
                                                            // 实现文件下载
                                                            public Task<IRemoteStreamContent> Download(Guid id)
                                                            {
                                                                // 使用文件流读取本地文件
                                                                var fs = new FileStream("C:\\Temp\\" + id + ".blob", FileMode.OpenOrCreate);
                                                                return Task.FromResult(
                                                                    (IRemoteStreamContent) new RemoteStreamContent(fs) {
                                                                        ContentType = "application/octet-stream"  // 设置媒体类型
                                                                    }
                                                                );
                                                            }


                                                            // 实现文件上传
                                                            public async Task Upload(Guid id, IRemoteStreamContent streamContent)
                                                            {
                                                                using (var fs = new FileStream("C:\\Temp\\" + id + ".blob", FileMode.Create))
                                                                {
                                                                    await streamContent.GetStream().CopyToAsync(fs); // 新疆接收到的流数据写入本地文件

                                                                    await fs.FlushAsync();
                                                                }
                                                            }

                                                            // 创建文件
                                                            public async Task CreateFileAsync(CreateFileInput input)
                                                            {
                                                                using (var fs = new FileStream("C:\\Temp\\" + input.Id + ".blob", FileMode.Create))
                                                                {
                                                                    await input.Content.GetStream().CopyToAsync(fs);
                                                                    await fs.FlushAsync();
                                                                }
                                                            }


                                                            // 创建多个文件
                                                            public async Task CreateMultipleFileAsync(CreateMultipleFileInput input)
                                                            {
                                                                using (var fs = new FileStream("C:\\Temp\\" + input.Id + ".blob", FileMode.Append))
                                                                {
                                                                    foreach (var content in input.Contents)
                                                                    {
                                                                        await content.GetStream().CopyToAsync(fs);
                                                                    }
                                                                    await fs.FlushAsync();
                                                                }
                                                            }
                                                        }
                                                    }

                                            (IRemoteStreamContent与Auto API Controller和Dynamic C#HTTP Proxy系统兼容。)
                    */

                    /*
                        八、应用服务生命周期

                                    应用程序服务的生命周期是瞬态，它们会自动注册到依赖注入系统。
                    */
        </script>
    </body>
</html>